import datetime
import json
import logging
import multiprocessing
import os.path
import threading
import socket
import time
from multiprocessing import Process, shared_memory
import struct
import h5py
import numpy as np
import traceback
from PySide6.QtCore import QThread

from main_ui.dock_scope import Scope_Widget


class Second_Process(Process):
    def __init__(self, queue1, queue2, queue3, queue4):
        super(Second_Process, self).__init__()
        self.queue_second_instruct = queue1
        self.queue_second_state = queue2
        self.queue_scope_data = queue3
        self.queue_logging = queue4
        self.queue_save_scope = multiprocessing.Queue(maxsize=0)
        self.initset()  # 进程初始化

    def initset(self):
        self.daemon = True              # 守护进程
        self.scope_channel_name = []    # 存储通道名字的列表
        self.scope_member_name = []     # 存储成员名称的列表

        # 保存数据相关
        self.save_scope_flag = False     # 保存scope数据的标志
        self.save_count = 0             # 保存数据的累加器，每10次保存一次

        # h5文件相关
        self.compression = None                         # 是否启用压缩
        self.scope_time_ext_flag = False                # scope保存是否启用时间后缀
        self.scope_time_ext_format = "%Y-%m%d-%H%M"     # scope保存的后缀格式
        # self.compression = 'gzip'
        self.h5file_name = 'scope_data.h5'  # 文件名称
        # self.dataset_rows = 1000
        self.config = None              # 配置参数
        self.dataset_columns = 50       # 默认个数
        self.interval_pack = 1          # 默认每包间隔
        self.h5file = None
        self.time_set = None
        self.data_set = None
        self.file_info_group = None

    def run(self):
        # 启动日志记录
        # logging.basicConfig(level=logging.DEBUG, filemode='a', filename='scope_process_err.log', encoding='utf-8',
        #                     format='%(asctime)s \n %(name)s - %(levelname)s \n %(module)s - %(lineno)d \n %(message)s \n')
        # self.logger2 = logging.getLogger(__name__)

        # # 获取共享内存
        self.share_mem_time = shared_memory.SharedMemory(name='scope_share_memory_time')
        self.share_mem_data = shared_memory.SharedMemory(name='scope_share_memory_data')
        self.share_mem_index = shared_memory.SharedMemory(name='scope_share_memory_index')

        # 启动接收指令的定时器
        self.timer_scope = threading.Timer(0.01, self.timer_main)
        self.timer_scope.start()
        self.timer_save = threading.Timer(0.01, self.timer_save_scope)
        self.timer_save.start()
        self.thread_recv_scope = Thread_Recv_Scope(self)
        self.thread_lock = threading.Lock()


    def timer_main(self):
        try:
            self.timer_scope = threading.Timer(0.1, self.timer_main)
            self.timer_scope.start()
            while self.queue_second_instruct.qsize():
                instruction = self.queue_second_instruct.get()
                if instruction[0:2] == [1, 1]:  # 连接端口1，并启动接收线程
                    self.thread_recv_scope.update_config(instruction[2])
                    self.scope_channel_name = instruction[3]
                    self.scope_member_name = instruction[5]
                    # print(self.scope_member_name)
                    self.interval_pack = instruction[4]
                    self.thread_recv_scope.scope.set_channels_num(len(instruction[3]), self.interval_pack)
                    self.dataset_columns = len(instruction[3])
                    self.create_share_memory(self.dataset_columns)
                    if self.thread_recv_scope.scope_conn():
                        self.queue_second_state.put([1, 1, 1])
                    else:
                        self.queue_second_state.put([1, 1, 0])
                elif instruction == [1, 0]:  # 断开端口1
                    self.thread_recv_scope.recv_scope_flag = False
                    time.sleep(0.1)
                    try:
                        if self.thread_recv_scope.client_scope:
                            self.thread_recv_scope.client_scope.close()
                            del self.thread_recv_scope.client_scope
                        self.queue_second_state.put([1, 0, 1])
                    except:
                        pass
                elif instruction[0:2] == [3, 1]:  # 接收scope数据
                    self.thread_recv_scope.recv_scope_flag = True
                    self.thread_recv_scope.start()
                elif instruction[0:2] == [3, 2]:  # 继续接收scope数据
                    pass
                elif instruction == [3, 0]:  # 停止接收scope数据
                    self.thread_recv_scope.recv_scope_flag = False

                elif instruction[0:2] == [4, 1]:  # 保存Scope数据到h5
                    self.h5file_name = instruction[2]
                    if self.scope_time_ext_flag:
                        # 如果存在该文件就删掉
                        if os.path.exists(self.h5file_name):
                            os.remove(self.h5file_name)

                        # 判断文件名称对文件path进行修改
                        if '.{' in self.h5file_name and '}' in self.h5file_name:
                            self.h5file_name = self.h5file_name.replace(self.h5file_name[self.h5file_name.find('.{'): self.h5file_name.find('}') + 1], '')
                        else:
                            pass

                        curr_time = '.{' + datetime.datetime.now().strftime(self.scope_time_ext_format) + '}'
                        self.h5file_name = self.h5file_name[0:self.h5file_name.find('.h5')] + curr_time + self.h5file_name[self.h5file_name.find('.h5'):]
                    else:
                        # 如果存在该文件就删掉
                        if os.path.exists(self.h5file_name):
                            os.remove(self.h5file_name)

                        # 判断文件名称对文件path进行修改
                        if '.{' in self.h5file_name and '}' in self.h5file_name:
                            self.h5file_name = self.h5file_name.replace(self.h5file_name[self.h5file_name.find('.{'): self.h5file_name.find('}') + 1], '')
                        else:
                            pass

                    if instruction[3] == 'None':
                        self.compression = None
                    else:
                        self.compression = instruction[3]
                    # my_logger.info('此次保存用的格式为:' + str(self.compression))
                    self.queue_logging.put([1, 1, '此次保存用的格式为:' + str(self.compression)])
                    try:
                        self.h5file = h5py.File(self.h5file_name, 'w')
                        self.file_info_group = self.h5file.create_group('file_info')
                        self.file_info_group.create_dataset('time_unit',
                                                            data=0.0001 * self.interval_pack,
                                                            dtype=np.float32)
                        channel_name = self.scope_channel_name
                        # 这里省略了dtype=h5py.special_dtype(vlen=str),没有发现区别
                        self.file_info_group.create_dataset('channel_name', data=channel_name)
                        member_name = self.scope_member_name
                        # print(member_name)
                        # 这里省略了dtype=h5py.special_dtype(vlen=str),没有发现区别
                        self.file_info_group.create_dataset('member_name', data=member_name)
                        create_time = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
                        self.file_info_group.create_dataset('create_time', data=create_time)
                        self.time_set = self.h5file.create_dataset('time',
                                                         shape=(0, 1),
                                                         maxshape=(None, 1),
                                                         dtype=np.uint32,
                                                         chunks=True,
                                                         compression=self.compression)
                        self.data_set = self.h5file.create_dataset('data',
                                                         shape=(0, self.dataset_columns),
                                                         maxshape=(None, self.dataset_columns),
                                                         dtype=np.float32,
                                                         chunks=True,
                                                         compression=self.compression)
                        self.save_scope_flag = True
                        self.thread_recv_scope.save_scope_flag = True
                    except Exception as e:
                        exc_info = traceback.format_exc()
                        print(e)
                        # my_logger.warning('h5py文件生成出错，可能原因为压缩格式有问题，请重试\n' + exc_info)
                        self.queue_logging.put([1, 2, 'h5py文件生成出错，可能原因为压缩格式有问题，请重试\n' + exc_info])
                        self.save_scope_flag = False
                        self.thread_recv_scope.save_scope_flag = False
                        self.queue_second_state.put([4, 0])

                elif instruction == [4, 0]:  # 停止保存scope数据
                    self.save_scope_flag = False
                    time.sleep(0.01)
                    self.thread_recv_scope.save_scope_flag = False
                    finish_time = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
                    if self.file_info_group:
                        self.file_info_group.create_dataset('finish_time', data=finish_time)
                    if self.h5file:
                        self.h5file.close()

                elif instruction[0] == 5:     # 设置是否添加后缀
                    self.scope_time_ext_flag = instruction[1]
                    self.scope_time_ext_format = instruction[2]

                elif instruction[0] == 200:  # 退出该进程,代替terminate方法以方便关闭日志系统
                    self.destroy_share_memory()
                    logging.shutdown()
        except Exception as e:
            exc_info = traceback.format_exc()
            # my_logger.error('第二进程接收指令定时器出现错误\n' + exc_info)
            self.queue_logging.put([1, 3, '第二进程接收指令定时器出现错误\n' + exc_info])
            self.queue_second_state.put([5, 0])

    def timer_save_scope(self):
        try:
            self.timer_save = threading.Timer(0.01, self.timer_save_scope)
            self.timer_save.start()
            if self.save_scope_flag:
                while self.queue_save_scope.qsize():
                    self.thread_lock.acquire()
                    head_time, scope_data = self.queue_save_scope.get()
                    self.data_set.resize(self.data_set.shape[0] + scope_data.shape[0], axis=0)
                    self.data_set[-scope_data.shape[0]:] = scope_data
                    self.time_set.resize(self.time_set.shape[0] + head_time.shape[0], axis=0)
                    self.time_set[-head_time.shape[0]:] = head_time
                    self.thread_lock.release()
            else:
                if self.queue_save_scope.qsize():
                    self.queue_save_scope.get()
        except Exception as e:
            exc_info = traceback.format_exc()
            # my_logger.error('第二进程接收指令定时器出现错误\n' + exc_info)
            self.queue_second_state.put([6, 3, '第二进程接收指令定时器出现错误\n' + exc_info])
            self.queue_second_state.put([5, 0])

    def read_json_compression(self):
        self.compression = self.operate_json.read('h5py的压缩方式')
        if self.compression is None:
            self.compression = None
            # self.operate_json.write('h5py的压缩方式', 'None')
        elif self.compression in ['None', 'none', 'NONE']:
            self.compression = None

    def create_share_memory(self, length):
        self.scope_time = np.ndarray((Scope_Widget.custom_mem_size, 1), dtype=np.uint32, buffer=self.share_mem_time.buf)
        self.scope_data = np.ndarray((Scope_Widget.custom_mem_size, length), dtype=np.float32,
                                     buffer=self.share_mem_data.buf)
        self.scope_index = np.ndarray((1,), dtype=np.uint32, buffer=self.share_mem_index.buf)

        self.scope_time.fill(0)
        self.scope_data.fill(0)
        self.scope_index.fill(0)

        self.thread_recv_scope.update_buf(self.scope_time, self.scope_data, self.scope_index)

    def destroy_share_memory(self):
        self.share_mem_time.close()
        self.share_mem_data.close()
        self.share_mem_index.close()
        self.share_mem_time.unlink()
        self.share_mem_data.unlink()
        self.share_mem_index.unlink()

class Thread_Recv_Scope(QThread):
    def __init__(self, second_process):
        super(Thread_Recv_Scope, self).__init__()
        self.second_process = second_process
        self.queue_second_state = second_process.queue_second_state
        self.queue_logging = second_process.queue_logging

        # 创建变量
        self.scope_time = None
        self.scope_data = None
        self.scope_index = None

        self.initset()

    def initset(self):
        self.client_scope = None
        self.recv_scope_flag = False
        self.scope = Scope_Decode(self.second_process)
        self.recv_max_size = 1460
        self.save_scope_flag = False
        self.config = {
            "port1": "1027",
            "port2": "1028",
            "IP": "192.168.1.11",
            "SN": "255.255.255.0",
            "GW": "192.168.1.1",
            "password": "12341234"
        }

    def update_config(self, config):
        """ 更新配置文件 """
        self.config = config

    def update_buf(self, time, data, index):
        self.scope_time = time
        self.scope_data = data
        self.scope_index = index

    def scope_conn(self):
        # self.read_com_dispose()

        self.conn_err = 0
        while True:
            try:
                time.sleep(0.01)
                self.client_scope = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
                self.client_scope.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, 15000000)  # 扩大buf
                self.client_scope.settimeout(0.1)   # 设置连接timout的时长，太快会无法连接，太慢会导致掉线丢失的数据过长
                self.client_scope.connect((self.config['IP'], int(self.config['port2'])))
                self.client_scope.settimeout(0.5)
                self.scope.buf = b''
                self.second_process.queue_second_state.put([1, 1, 1])
                return True
            except TimeoutError:
                if self.conn_err > 3:
                    exc_info = traceback.format_exc()
                    # my_logger.debug('未连接成功\n' + exc_info)
                    self.queue_logging.put([1, 0, '未连接成功\n' + exc_info])
                    return False
                self.conn_err += 1
                self.second_process.queue_second_state.put([1, 1, 0])
            except ConnectionRefusedError:
                if self.conn_err > 3:
                    exc_info = traceback.format_exc()
                    # my_logger.debug('远程的连接还未断开\n' + exc_info)
                    self.queue_logging.put([1, 0, '远程的连接还未断开\n' + exc_info])
                    return False
                self.conn_err += 1
                self.second_process.queue_second_state.put([1, 1, 0])
            except OSError:
                exc_info = traceback.format_exc()
                # my_logger.debug('已关闭套接字\n' + exc_info)
                self.queue_logging.put([1, 0, '已关闭套接字\n' + exc_info])
                return False
            except Exception as e:
                pass

    def run(self):
        # my_logger.info('第二进程已运行')
        self.queue_logging.put([1, 1, '第二进程已运行'])
        while self.recv_scope_flag:
            try:
                dataframe = self.client_scope.recv(self.recv_max_size)
            except ConnectionAbortedError:
                self.scope.recnn_flag = True
                exc_info = traceback.format_exc()
                # my_logger.debug('接收scope时远程断开连接\n' + exc_info)
                if self.recv_scope_flag:
                    self.queue_logging.put([1, 2, '接收scope时远程断开连接\n' + exc_info])
                else:
                    self.queue_logging.put([1, 0, '接收scope时远程断开连接\n' + exc_info])
                # 重新连接并继续接收
                if self.recv_scope_flag:
                    if self.client_scope:
                        self.client_scope.close()
                        del self.client_scope
                    if self.scope_conn():
                        # print(3, time.time())
                        self.second_process.queue_second_state.put([3, 2])
                    else:
                        # my_logger.warning('第二端口连接3次失败请重试')
                        self.queue_logging.put([1, 2, '第二端口连接3次失败请重试'])
                continue
            except ConnectionResetError:
                self.scope.recnn_flag = True
                exc_info = traceback.format_exc()
                # my_logger.debug('接收scope时远程断开连接\n' + exc_info)
                if self.recv_scope_flag:
                    self.queue_logging.put([1, 2, '接收scope时远程断开连接\n' + exc_info])
                else:
                    self.queue_logging.put([1, 0, '接收scope时远程断开连接\n' + exc_info])
                # 重新连接并继续接收
                if self.recv_scope_flag:
                    if self.client_scope:
                        self.client_scope.close()
                        del self.client_scope
                    if self.scope_conn():
                        self.second_process.queue_second_state.put([3, 2])
                    else:
                        # my_logger.warning('第二端口连接3次失败请重试')
                        self.queue_logging.put([1, 2, '第二端口连接3次失败请重试'])
                continue
            except TimeoutError:
                self.scope.recnn_flag = True
                exc_info = traceback.format_exc()
                # my_logger.debug('接收scope超时造成的重连\n' + exc_info)
                if self.recv_scope_flag:
                    self.queue_logging.put([1, 2, '接收scope超时造成的重连\n' + exc_info])
                else:
                    self.queue_logging.put([1, 0, '接收scope超时造成的重连\n' + exc_info])
                # 重新连接并继续接收
                if self.recv_scope_flag:
                    if self.client_scope:
                        self.client_scope.close()
                        del self.client_scope
                    if self.scope_conn():
                        self.second_process.queue_second_state.put([3, 2])
                    else:
                        # my_logger.warning('第二端口连接3次失败请重试')
                        self.queue_logging.put([1, 2, '第二端口连接3次失败请重试'])
                continue
            except OSError:
                pass
            # 对返回来的数据进行处理
            try:
                head_time, scope_data = self.scope.decode(dataframe)
            except:
                head_time = None
            # print('处理数据的时间:', c)
            # time3 = time.time()
            if head_time is None:
                pass
            elif str(head_time) == 'break':
                # my_logger.warning('连续三次数据检查出错，需要进行自动重连')
                self.queue_logging.put([1, 2, '连续三次数据检查出错，需要进行自动重连'])
                # 如果在读取的话重新连接并继续接收
                if self.recv_scope_flag:
                    if self.client_scope:
                        self.client_scope.close()
                        del self.client_scope
                    if self.scope_conn():
                        self.second_process.queue_second_state.put([3, 2])
                    else:
                        # my_logger.warning('第二端口连接3次失败请重试')
                        self.queue_logging.put([1, 2, '第二端口连接3次失败请重试'])
            else:
                self.del_data(head_time, scope_data)
            # 如果处理速度不够快就多处理几次
            while len(self.scope.buf) >= self.scope.tcp_num + self.scope.tcp_head_num:
                # print('确实出现了')
                head_time, scope_data = self.scope.decode(b'')
                if head_time is None:
                    pass
                elif str(head_time) == 'break':
                    # my_logger.warning('连续三次数据检查出错，需要进行自动重连')
                    self.queue_logging.put([1, 2, '连续三次数据检查出错，需要进行自动重连'])
                    # 如果在读取的话重新连接并继续接收
                    if self.recv_scope_flag:
                        if self.client_scope:
                            self.client_scope.close()
                            del self.client_scope
                        if self.scope_conn():
                            self.second_process.queue_second_state.put([3, 2])
                        else:
                            # my_logger.warning('第二端口连接3次失败请重试')
                            self.queue_logging.put([1, 2, '第二端口连接3次失败请重试'])
                else:
                    self.del_data(head_time, scope_data)


    # # 关闭日志
    # logging.shutdown()

    def del_data(self, head_time, scope_data):
        self.scope_time[self.scope_index[0]:self.scope_index[0] + len(head_time), :] = head_time
        self.scope_data[self.scope_index[0]:self.scope_index[0] + len(scope_data), :] = scope_data
        self.scope_index[0] += len(head_time)
        if self.scope_index[0] >= Scope_Widget.custom_mem_size:
            self.scope_index[0] = 0

        if self.save_scope_flag:
            self.second_process.queue_save_scope.put([head_time, scope_data])


# 翻转接收的32位数据
class Scope_Decode:
    def __init__(self, second_process):
        self.second_process = second_process
        self.queue_second_state = self.second_process.queue_second_state
        self.queue_logging = self.second_process.queue_logging
        self.initVar()
        self.err_cont = 0  # 记录出错次数
        self.buf = b''
        self.dataframe2 = b''
        self.time = (0,)
        self.recnn_flag = False
        self.last_time = 0

    # 初始化变量
    def initVar(self):
        self.channels_num = 20  # 通道数量
        self.piece_num = 100  # 一次传输的单通道数量
        self.tcp_head_num = 8  # 一次传输Tcp头的长度 FF FF FF FF + 4位时间
        self.tcp_num = self.channels_num * self.piece_num * 4 + self.tcp_head_num  # 一条tcp的数量


    def set_channels_num(self, num, interval_pack):
        self.channels_num = num
        self.piece_num = int(100 / interval_pack)
        self.tcp_num = int(self.channels_num * self.piece_num * 4 + self.tcp_head_num)  # 一条tcp的数量

        # print(self.tcp_num)

    def unpack(self, dataframe):
        head = struct.unpack('<L', dataframe[0:4])[0]
        head_time = np.arange(head * self.piece_num, head * self.piece_num + self.piece_num, 1).astype(
            np.uint32).reshape(self.piece_num, 1)
        data = struct.unpack('<' + 'f' * self.channels_num * self.piece_num, dataframe[4:])
        scope_data = np.array(data, dtype=np.float32, order='C').reshape(self.piece_num, self.channels_num)
        return head_time, scope_data

    def decode(self, tcpdata):
        if self.err_cont == 3:
            print('超了')
            self.err_cont = 0
            return 'break', None
        tcpdata = self.buf + tcpdata
        # 有足够长的数据之后
        if len(tcpdata) >= self.tcp_num + self.tcp_head_num:
            # 是以8个F开头的话
            if tcpdata[0:4] == b'\xFF\xFF\xFF\xFF':
                if tcpdata[self.tcp_num:self.tcp_num + 4] == b'\xFF\xFF\xFF\xFF':
                    if struct.unpack('<' + 'L', tcpdata[4:8])[0] == \
                            struct.unpack('<' + 'L', tcpdata[self.tcp_num + 4:self.tcp_num + 8])[0] - 1:
                        self.err_cont = 0
                        self.buf = tcpdata[self.tcp_num:]
                        return self.unpack(tcpdata[4:self.tcp_num])
                    else:
                        self.err_cont += 1
                        # my_logger.debug('时间帧对应不上\n' + 'first_frame_time :' + str(
                        #     struct.unpack('<L', tcpdata[4:8])[0]) + '\nsecond_frame_time:' + str(
                        #     struct.unpack('<L', tcpdata[self.tcp_num + 4:self.tcp_num + 8])[0]) + '\n')

                        self.queue_logging.put([1, 0, '时间帧对应不上\n' + 'first_frame_time :' + str(
                            struct.unpack('<L', tcpdata[4:8])[0]) + '\nsecond_frame_time:' + str(
                            struct.unpack('<L', tcpdata[self.tcp_num + 4:self.tcp_num + 8])[0])])


                        # with open('err.log', 'a+', encoding='utf-8') as f:
                        #     f.write(str(datetime.datetime.now()) + '\n' + '时间帧对应不上\n' + 'first_frame_time :' + str(struct.unpack('<L', tcpdata[4:8])[0]) + '\nsecond_frame_time:' + str(struct.unpack('<L', tcpdata[self.tcp_num + 4:self.tcp_num + 8])[0]) + '\n')
                        self.buf = tcpdata[self.tcp_num:]
                        return None, None
                else:
                    self.err_cont += 1
                    if b'\xff\xff\xff\xff' in tcpdata[4:]:
                        index = tcpdata[4:].index(b'\xFF\xFF\xFF\xFF')
                        tcpdata = tcpdata[(index + 4):]
                        # my_logger.debug(
                        #     '上一帧的8个F和下一帧的8个F中间的数据长度不对\n' + '长度为' + str(
                        #         index) + 'byte\n')
                        self.queue_logging.put([1, 0, '上一帧的8个F和下一帧的8个F中间的数据长度不对\n' + '长度为' + str(index) + 'byte'])

                        # with open('err.log', 'a+', encoding='utf-8') as f:
                        #     f.write(
                        #         str(datetime.datetime.now()) + '\n' + '上一帧的8个F和下一帧的8个F中间的数据长度不对\n' + '长度为' + str(
                        #             index) + 'bit\n')
                    else:
                        tcpdata = b''
                    # my_logger.debug('在超过一帧的长度里没有找到第二个8个F\n')
                    self.queue_logging.put([1, 0, '在超过一帧的长度里没有找到第二个8个F'])
                    # with open('err.log', 'a+', encoding='utf-8') as f:
                    #     f.write(str(datetime.datetime.now()) + '\n' + '在超过一帧的长度里没有找到第二个8个F\n' + '\n')
                    self.buf = tcpdata
                    return None, None
            else:
                self.err_cont += 1
                # my_logger.debug('起始数据不是8个F\n')
                self.queue_logging.put([1, 0, '起始数据不是8个F'])
                # with open('err.log', 'a+', encoding='utf-8') as f:
                #     f.write(str(datetime.datetime.now()) + '\n' + '起始数据不是8个F' + '\n')
                if b'\xff\xff\xff\xff' in tcpdata:
                    tcpdata = tcpdata[tcpdata.index(b'\xff\xff\xff\xff'):]
                else:
                    tcpdata = b''
                self.buf = tcpdata
                return None, None
        else:
            self.buf = tcpdata
            return None, None

